package com.lyloou.practice.factory;

import com.google.common.base.Strings;
import com.lyloou.practice.annotation.*;
import com.lyloou.practice.utils.StringUtils;
import org.reflections.Reflections;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author lyloou
 * @date 2020/06/04
 * @desc 注解Bean工厂
 */
public class AnnotationBeanFactory {
    private static final Map<String, Object> map = new HashMap<>();

    public static void init(String packageName) throws Exception {
        // https://www.cnblogs.com/lspz/p/6933590.html
        Reflections reflections = new Reflections(packageName);

        // 扫描 Component、Service 、Repository 注解标注的类
        Set<Class<?>> set = getClasses(reflections);
        for (Class<?> aClass : set) {
            String id = getId(aClass);
            Object instance = Class.forName(aClass.getName()).newInstance();
            map.put(id, instance);
        }

        // 再分配属性值
        for (Class<?> aClass : set) {
            // https://stackoverflow.com/questions/4296910/is-it-possible-to-read-the-value-of-a-annotation-in-java

            String id = getId(aClass);

            Object obj = map.get(id);

            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field field : declaredFields) {
                String key = field.getName();
                // https://stackoverflow.com/questions/4453159/how-to-get-annotations-of-a-member-variable
                boolean annotationPresent = field.isAnnotationPresent(Autowired.class);
                if (annotationPresent) {
                    // 有 Qualifier 标注时，可以有这个名称
                    if (field.isAnnotationPresent(Qualifier.class)) {
                        Qualifier annotation = field.getAnnotation(Qualifier.class);
                        key = annotation.value();
                    }

                    Object property = map.get(key);
                    if (property == null) {
                        throw new RuntimeException(String.format("此实例尚不存在：%s", key));
                    }
                    setField(obj, property, field);
                }
            }
        }

    }


    private static Set<Class<?>> getClasses(Reflections reflections) {
        Class<? extends Annotation>[] classes = new Class[]{Service.class, Repository.class, Component.class};
        Set<Class<?>> set = new HashSet<>();
        for (Class<? extends Annotation> aClass : classes) {
            Set<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(aClass);
            set.addAll(typesAnnotatedWith);
        }

        return set;
    }

    private static String getId(Class<?> aClass) {
        Annotation[] declaredAnnotations = aClass.getDeclaredAnnotations();
        String id = null;
        for (Annotation declaredAnnotation : declaredAnnotations) {
            if (declaredAnnotation.annotationType() == Service.class) {
                Service service = aClass.getAnnotation(Service.class);
                id = service.value();
            }

            if (declaredAnnotation.annotationType() == Component.class) {
                Component service = aClass.getAnnotation(Component.class);
                id = service.value();
                break;
            }

            if (declaredAnnotation.annotationType() == Repository.class) {
                Repository service = aClass.getAnnotation(Repository.class);
                id = service.value();
                break;
            }
        }

        // 没有设置 value 时，使用首字母小写的类名来作为默认值
        if (Strings.isNullOrEmpty(id)) {
            id = StringUtils.toLowerCaseFirstOne(aClass.getSimpleName());
        }
        return id;
    }

    private static void setField(Object obj, Object property, Field field) throws IllegalAccessException {
        // 获取原值
        boolean accessible = field.isAccessible();

        // 设置访问性
        field.setAccessible(true);
        field.set(obj, property);

        // 还原
        field.setAccessible(accessible);
    }

    public static Object getBean(String id) {
        return map.get(id);
    }

    public static <T> T getBean(Class clazz) {
        return (T) map.get(StringUtils.toLowerCaseFirstOne(clazz.getSimpleName()));
    }
}
